package baseFx.common.remote.http;

import baseFx.common.Assert;
import baseFx.common.Dynamic;
import baseFx.common.EventDispatcher;
import baseFx.common.concurrent.Task;
import baseFx.common.remote.ProxyProvider;
import baseFx.common.remote.Remote;
import baseFx.common.utils.*;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import java.util.*;

public class HttpProxyProvider implements ProxyProvider {
    public int retryCount = 3;
    public final EventDispatcher<InvokeArgs> sendBefore = new EventDispatcher<>(firekey);

    private static String firekey = UUID.randomUUID().toString();
    private Map<Class<?>, HttpProxy> proxyMap = new HashMap<>();

    @Override
    public <T> T get(Class<T> clazz) {
        HttpProxy handler = proxyMap.get(clazz);
        if (handler == null) {
            handler = new HttpProxy(this);
            proxyMap.put(clazz, handler);
        }
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
    }

    protected String getHost(Method method) {
        return "";
    }

    protected String getResponse(InvokeArgs args) throws IOException {
        String ps = "";
        if (args.params.size() > 0) {
            List<String> list = new ArrayList<>();
            for (HttpParameter hp : args.params) {
                String value = serializer(hp.parameter, hp.value);
                if (value != null) {
                    value = UrlUtils.encode(value);
                    list.add(hp.name + "=" + value);
                }
            }
            ps = String.join("&", list);
        }
        HttpURLConnection res = null;
        String url = args.url;
        if ("get".equalsIgnoreCase(args.httpMethod)) {
            if (StringUtils.hasValue(ps)) {
                url += url.contains("?") ? "&" : "?";
                url += ps;
            }
            return getRequest(url);
        } else if ("post".equalsIgnoreCase(args.httpMethod)) {
            return postRequest(url, ps);
        } else {
            throw new RuntimeException("无法识别的http类型：" + args.httpMethod);
        }
    }

    protected String postRequest(String url, String body) throws IOException {
        HttpURLConnection resp = HttpUtils.rawPost(url, body, null);
        if (resp.getResponseCode() == 200) {
            return StringReader.readToEnd(resp.getInputStream());
        } else {
            throw new ResponseException(resp.getResponseCode(), resp.getResponseMessage());
        }
    }

    protected String getRequest(String url) throws IOException {
        HttpURLConnection resp = HttpUtils.rawGet(url);
        if (resp.getResponseCode() == 200) {
            return StringReader.readToEnd(resp.getInputStream());
        } else {
            throw new ResponseException(resp.getResponseCode(), resp.getResponseMessage());
        }
    }

    protected String serializer(Parameter parameter, Object value) {
        if (value == null) {
            return null;
        }
        return value.toString();
    }

    protected Object deserializer(Method method, String response) {
        if (StringUtils.isNullOrEmpty(response)) {
            return null;
        }
        return ConvertUtils.change(response, method.getReturnType());
    }

    protected boolean isRetry(InvokeArgs args, Exception error) {
        if (error instanceof IOException) {
            Task.delay(5000);
            return true;
        }
        return false;
    }

    private static class HttpProxy implements InvocationHandler {
        private HttpProxyProvider provider;

        public HttpProxy(HttpProxyProvider provider) {
            this.provider = provider;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Remote attr = method.getAnnotation(Remote.class);
            if (attr == null) {
                return method.invoke(this, args);
            }
            if (args == null) {
                args = new Object[0];
            }
            String hm = "post";
            String path = attr.path();
            if (path.startsWith("http://") == false && path.startsWith("https://") == false) {
                int pos = path.indexOf(":");
                if (pos > 0) {
                    hm = path.substring(0, pos).toLowerCase();
                    switch (hm) {
                        case "get":
                        case "post":
                            path = path.substring(pos + 1);
                            break;
                        default:
                            throw new RuntimeException("无法识别的请求类型：" + hm);
                    }
                }
            }
            if (path.length() == 0) {
                path = method.getDeclaringClass().getSimpleName() + "/" + method.getName();
            } else if (path.startsWith("/") == false && path.contains(":/") == false) {
                path = method.getDeclaringClass().getSimpleName() + "/" + path;
            }
            InvokeArgs iea = new InvokeArgs(method, hm);
            iea.url = path.contains(":/") ? path : provider.getHost(method) + "/" + path;

            int real = 0;
            for (Object pv : args) {
                Parameter mp = method.getParameters()[iea.params.size()];
                Expan expan = mp.getAnnotation(Expan.class);
                if (pv == null) {
                    real++;
                    HttpParameter hp = new HttpParameter(mp, "");
                    iea.params.add(hp);
                } else if (expan == null) {
                    real++;
                    HttpParameter hp = new HttpParameter(mp, attr.paramNames()[iea.params.size()]);
                    hp.value = pv;
                    iea.params.add(hp);
                } else {
                    Dynamic fs = new Dynamic(pv);
                    for (String item : fs.getFields()) {
                        String fv = fs.getString(item);
                        if (StringUtils.hasValue(fv)) {
                            HttpParameter hp = new HttpParameter(mp, item);
                            hp.value = fv;
                            iea.params.add(hp);
                        }
                    }
                }
            }
            Assert.isEquals(attr.paramNames().length, real, "指定的参数名数量与定义不一致");
            provider.sendBefore.fire(firekey, provider, iea);
            String rs = null;
            if (iea.response != null) {
                rs = iea.response;
            } else {
                while (true) {
                    try {
                        rs = provider.getResponse(iea);
                        break;
                    } catch (Exception ex) {
                        if (provider.isRetry(iea, ex) == false) {
                            throw ex;
                        }
                    }
                }
            }
            if (rs == null || method.getReturnType() == void.class) {
                return null;
            } else {
                return provider.deserializer(method, rs);
            }
        }
    }
}
